#include "config.h"

#include <sys/statvfs.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/statfs.h>

#define DBG_SUBSYS S_LIBSTORAGE

#include "sysutil.h"
#include "configure.h"
#include "cluster.h"
#include "../replica/clock.h"
#include "dbg.h"

typedef struct {
        chkid_t chkid;
        fileid_t oid;
        uint64_t version;
} unit_t;

static unit_t *__unit__;
static uint64_t  __total__ = 0;
static uint32_t __ltime__ = 0;

inline static int __test_reference_multi(int count)
{
        int ret, i;
        chkid_t chkid[SNAPSHOT_MAX];
        fileid_t oid[SNAPSHOT_MAX];

        YASSERT(count <= SNAPSHOT_MAX);

        for (i = 0; i < count; i++) {
                chkid[i].version = __ltime__;
                chkid[i].id = i;
                chkid[i].type = __RAW_CHUNK__;

                oid[i].version = __ltime__;
                oid[i].id = i + SNAPSHOT_MAX;
                oid[i].type = __OBJECT_CHUNK__;
        }

        ret = diskmd_set_reference(chkid, oid, count);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

static int __test_gen_data(int count)
{
        int ret, i;
        unit_t *unit;

        ret = ymalloc((void **)&__unit__, sizeof(*unit) * count);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        for (i = 0; i < count; i++) {
                unit = &__unit__[i];
                unit->chkid.version = __ltime__;
                unit->chkid.id = i + count + 10;
                unit->chkid.type = __RAW_CHUNK__;
                unit->oid.version = __ltime__;
                unit->oid.id = i;
                unit->oid.type = __OBJECT_CHUNK__;
        }

        __total__ = count;

        return 0;
err_ret:
        return ret;
}

static int __test_vclock_insert(uint64_t count)
{
        int ret, idx;
        vclock_t vclock;
        unit_t *unit;
        job_t *job;
        job_handler_t handler;
        uint64_t i;

        for (i = 0; i < count; i++) {
                idx = _random() % __total__;
                unit = &__unit__[idx];
                vclock.version = unit->version++;
                vclock.nid.id.id = 0;
                vclock.nid.id.version = 0;

                if (idx % 2 == 0) {
                        ret = job_create(&job, NULL, "test_vclock");
                        if (unlikely(ret))
                                GOTO(err_ret, ret);

                        job_wait_init(job);
                        handler = job_handler(job, 0);
                        ret = diskmd_set_vclock(&unit->chkid, &vclock, &handler);
                        if (unlikely(ret))
                                GOTO(err_job, ret);

                        ret = job_timedwait(job, 5);
                        if (unlikely(ret))
                                GOTO(err_job, ret);

                        job_destroy(job);
                } else {
                        ret = diskmd_set_vclock(&unit->chkid, &vclock, NULL);
                        if (unlikely(ret))
                                GOTO(err_job, ret);
                }
        }


        return 0;
err_job:
        job_destroy(job);
err_ret:
        return ret;
}

static int __test_vclock()
{
        int ret;

        ret = __test_gen_data(1024 * 10);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __test_vclock_insert(1024 * 1024 * 3);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

typedef struct {
        sem_t sem;
        fileid_t oid;
        int count;
        chkid_t *chkid;
} chunk_array_t;

#define MAX_REF 8

static int __test_reference_gen_data(chunk_array_t *array, int count)
{
        int ret, chknum, i;
        chkid_t *chkid;

        chknum = MAX_REF * 8192;
        ret = ymalloc((void **)&chkid, sizeof(*chkid) * chknum);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        for (i = 0; i < chknum; i++) {
                chkid[i].id = i;
                chkid[i].type = __RAW_CHUNK__;
                chkid[i].version = __ltime__ + 10;
        }

        for (i = 0; i < count; i++) {
                array[i].oid.id = i;
                array[i].oid.type = __OBJECT_CHUNK__;
                array[i].oid.version = __ltime__ + 20;
                array[i].count = chknum;
                array[i].chkid = chkid;;
        }

        return 0;
err_ret:
        return ret;
}

static void *__test_commit_worker(void *arg)
{
        int ret, i;
        chunk_array_t *array;

        array = arg;

        for (i = 0; i < array->count; i++) {
                ret = diskmd_set_reference(&array->chkid[i], &array->oid, 1);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        for (i = 0; i < array->count; i++) {
                ret = diskmd_set_dereference(&array->chkid[i], &array->oid);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        sem_post(&array->sem);

        return NULL;
err_ret:
        UNIMPLEMENTED(__DUMP__);
        return NULL;
}

static int __test_reference()
{
        int ret, i, count;
        pthread_t th;
        pthread_attr_t ta;
        chunk_array_t array[MAX_REF];

        count = MAX_REF;
        ret = __test_reference_gen_data(array, count);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        for (i = 0; i < count; i++) {
                (void) pthread_attr_init(&ta);
                (void) pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);

                ret = sem_init(&array[i].sem, 0, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = pthread_create(&th, &ta, __test_commit_worker, &array[i]);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        for (i = 0; i < count; i++) {
                ret = sem_wait(&array[i].sem);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int main()
{
        int ret;
        char path[MAX_PATH_LEN];
        struct stat stbuf;

        ret = env_init_simple("test_diskmd_create");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        //snprintf(path, MAX_LINE_LEN, "%s/test/test_md", gloconf.home);
        snprintf(path, MAX_LINE_LEN, SHM_ROOT"/test_md");
        DINFO("test in %s\n", path);

        ret = stat(path, &stbuf);
        if (ret == 0) {
                ret = EEXIST;
                DERROR("%s exist\n", path);
                GOTO(err_ret, ret);
        }

        __ltime__ = gettime();

        ret = diskmd_init(path);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __test_reference();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __test_vclock();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}
